Repository navigation
feat(ri-exchange): pass offered instance types to reshape analysis - #1246
Conversation
…e reshape Pins pkg to LeanerCloud/cloud-commitments-go#584, whose reshape analysis accepts the region's offered instance types (WithOfferedInstanceTypes and RunAutoExchangeParams.OfferedInstanceTypes). The change is source-compatible; nothing is wired to it yet. The range also brings go#578 (RunAutoExchange fails closed on any mode other than "auto" or "manual") and go#584's skip of equal-capacity reshapes. Refs LeanerCloud#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…shape page getReshapeRecommendations now fetches the region's instance type catalog (ec2svc.Client.GetValidResourceTypes, DescribeInstanceTypeOfferings) and passes it to AnalyzeReshapingWithRecs, so targets are limited to offered sizes and a family the region does not offer gets a recommendation with a Reason instead of a target. A catalog error or an empty catalog logs a warning naming the region and falls back to the legacy size ladder. Nothing is cached, so the next request queries the catalog again. Refs LeanerCloud#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r reshapes The standalone RI-exchange task and the ladder's exchange runner now fetch the region's instance type catalog (GetValidResourceTypes) and pass it as RunAutoExchangeParams.OfferedInstanceTypes, so automated exchanges only target offered sizes and skip, with a reason, RIs whose family the region does not offer. Both paths now build their clients through newRIExchangeClients, and the ladder runner's post-construction body moves to runWithClients so tests can drive it with fakes. A catalog error or empty catalog logs a warning naming the region and keeps the legacy size ladder, uncached. Refs LeanerCloud#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reachedYou've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. Next included review available in 58 minutes. View limit details
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Brings in main's CI fixes (LeanerCloud#1236, LeanerCloud#1252) and CI changes since, so this PR gets a fresh CI run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review 2 (claude-fleet, cold): APPROVE 5bcb13b Merge order note: this PR bumps the pkg pin from 541e05e to e237996, and that range includes go#578.
Findings (none blocking on their own):
Attacked:
Verified locally, with GOTOOLCHAIN=go1.26.10 and no cloud credentials, under the heavy lock:
Reviewed by an independent agent with no author context (second reviewer). Review only: nothing was pushed, and the GitHub review state is unchanged. |
|
Merge-order note: this pin bump (541e05e..e237996) includes LeanerCloud/cloud-commitments-go#578, which makes the RI-exchange mode check fail closed. It should merge only after #1243 (ri_exchange_mode validation on PUT /api/config) is fixed and merged, and after the legacy-row query in #1243's body shows no stored mode other than |
|
VERDICT: APPROVE 5bcb13b PR #1246 (draft, @IAmJSD), head 5bcb13b, merge base 503eb2e (3 behind origin/main 2ded648). Findings (no blockers)
#1206 checklist vs code
Go-lib range (pkg-only source diff from the module cache)
Attacked / probes (all killed)
|
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: REQUEST_CHANGES Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rA1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
Bring the branch up to date with main (7cde88e), which now validates RI exchange account IDs as 12-digit AWS account IDs (LeanerCloud#1170). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Main now refuses to record an RI exchange whose account ID is not a 12-digit AWS account ID (LeanerCloud#1170), so the "acct-1" fixture failed three standalone/* subtests of TestRIExchangeReshape_OfferedCatalog. Use the 123456789012 documentation placeholder instead; every assertion is kept. Refs LeanerCloud#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Pick up LeanerCloud#1111 and LeanerCloud#1418 so CI runs against the fixed main (LeanerCloud#1418 fixes main's red purchase integration test). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Updated the branch. New head:
Checks on the merged tree:
|
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
From earlier reviews (confirmed): #1243 is still in REQUEST_CHANGES, so "merge after #1243" is not yet satisfiable. The stale Reviewer: rB24. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
…1468) The reshape analyzer returns target_instance_type "" and target_count 0 when it has no exchange target for an RI (a completely unused RI, or, since the go bump in #1246, a target capped at the units the account owns). The table rendered those rows as "0x " under Suggested and kept a live Exchange button that opened the modal with a target count of 0. Such rows now say "No exchange target" (the Reason column already explains why) and get a disabled Exchange button with no quote action. A no-target row that still carries cross-family alternatives keeps a live button, since the modal can preselect from them, and opens it with a count of 1 instead of 0. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Conflict resolution: - go.mod/go.sum: main's pin (88b95b6, LeanerCloud#1490) already contains e237996 (go#584), so main's side is taken and this branch no longer moves the pin. - handler_ri_exchange.go: keep main's fail-closed STS account resolution and fourEyesExchangeMode; build clients through newRIExchangeClients so the standalone run gets the offered instance type catalog. - ladder_write.go: keep main's run() with the 4-eyes downgrade (LeanerCloud#1108) and build clients through newRIExchangeClients; RunAutoExchangeParams gains OfferedInstanceTypes. The offered-catalog ladder test calls run(). Test follow-ups for main's changes: fixtures added on main (4-eyes, account resolution) get offeredInstanceTypes, and the offered-catalog fixtures carry a price because main excludes unpriced convertible RIs (LeanerCloud#1188/LeanerCloud#1191). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
govulncheck in the Docker image build now fails on these two advisories, both fixed in x/net v0.62.0. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rG3. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): VERDICT: APPROVE Blocking:
Non-blocking:
Attacked:
Body-stale:
Reviewer: rH1. Bar while the maintainers are away (set by IAmJSD as acting owner): one approving independent review on the head, two for money, auth and security paths. |
|
Local verification at |
|
Merged by IAmJSD's agent at IAmJSD's request (IAmJSD is acting owner while the maintainers are away), under the bar IAmJSD set: 2 approving independent review(s) on |
…nge-wiring Conflicts in internal/api/handler_ri_exchange.go and ri_utilization_cache.go: kept main's offered-instance-types wiring (LeanerCloud#1246) and per-account RI utilization cache key (LeanerCloud#1553), expressed through the provider_wiring.go aliases so the handler still imports no concrete provider package. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Root cause
The reshape analysis in
cloud-commitments-gocan take the region's offered instance types since LeanerCloud/cloud-commitments-go#584 (exchange.WithOfferedInstanceTypes,RunAutoExchangeParams.OfferedInstanceTypes). The platform never passed a catalog. All three reshape paths therefore used the legacy size ladder. They could recommend a size the region doesn't sell, or a target in a family the region doesn't offer at all.Fix
mainpinscloud-commitments-goat88b95b6(chore(deps): bump cloud-commitments-go to 88b95b6 and keep GCP recommendations when some are skipped #1490), which contains go#584 (e237996, checked withgit merge-base --is-ancestor). The branch's original pin-bump commit is superseded: the refresh merge takesmain'sgo.mod/go.sumexactly.internal/api):reshapeEC2ClientgainsGetValidResourceTypes.getReshapeRecommendationspassesexchange.WithOfferedInstanceTypes(...)toAnalyzeReshapingWithRecs. The new exportedapi.OfferedInstanceTypes(ctx, region, fetch)wraps the catalog fetch. On an error or an empty result it logs apkg/loggingwarning that names the region, then returns nil, so analysis falls back to the legacy ladder. It caches nothing, so every request queries the catalog again and a fallback is never reused.internal/server): the standalone task (handler_ri_exchange.go) and the ladder'sexchangeRunnerAdapter(ladder_write.go) both setRunAutoExchangeParams.OfferedInstanceTypesthrough the same helper. Both now build their clients withnewRIExchangeClients, so the wiring is shared and tested. Everything else on these paths ismain's: the standalone run keeps the fail-closed STS account resolution andfourEyesExchangeMode, and the ladder adapter keepsrun(), which applies fix(ri-exchange): run unattended exchanges as manual while 4-eyes is on #1108's 4-eyes downgrade (unattended exchanges run as manual under 4-eyes, and an unreadable global config stops the run before any AWS call). The offered-catalog wiring sits on top of that.IAM:
ec2:DescribeInstanceTypeOfferingsis already granted in the Lambda/Fargate runtime roles, the CloudFormation stacks and the federation templates, so no IaC change is needed. If a deployment lacks the grant, the call fails, logs the warning and keeps the old behavior.Closes #1206
Refs #1196
Merge order
This PR used to be held behind #1243 (
ri_exchange_modevalidation onPUT /api/config, the fix for #1196), because its pin bump would have brought in go#578's fail-closed exchange mode. That hold no longer applies:mainalready has go#578 through #1466 (pin9cec521, now88b95b6), and this PR no longer changes the pin. #1243 is still the companion fix for #1196 and can merge independently. The #1108 conflict ininternal/server/ladder_write.gohas been resolved in this branch, keeping #1108's 4-eyes downgrade.Behavior changes to be aware of
The go-side changes this PR originally pulled in through its pin bump (go#584 reshape targets, go#578 fail-closed mode and cancel failure, go#579 service-details decoding, go#577 reservation naming) are already on
mainthrough #1466 and #1490. The behaviour this PR itself adds:Reason: "instance family not offered in region (<family>)..."and no target. Scheduled and ladder runs record it inSkippedwith that reason and never look up an offering or request a quote for it.DescribeInstanceTypeOfferingscall (paginated at 1000). I added no caching. The issue requires that the fallback not be cached, and caching successful catalogs could be a follow-up if the latency matters.Frontend follow-up for the UI owner
No frontend files changed. Recommendations can now arrive with an empty
target_instance_typeand areasonsaying the family isn't offered in the region. The reshape table should show the reason rather than an empty target, or treat these like the existing "completely unused" no-target rows.Refresh notes
origin/mainwas merged into the branch (merge commit, no rebase). Conflicts:go.mod/go.sum:main's side, as above.internal/server/handler_ri_exchange.go:main's fail-closedresolveAccountID+newRIExchangeClients(..., accountID).internal/server/ladder_write.go:main'srun()(4-eyes) +newRIExchangeClients+OfferedInstanceTypesin the params. The branch'srunWithClientssplit is dropped in favour ofmain'srun(), and the offered-catalog ladder test now callsrun().Test follow-ups for
main's changes: fixtures added onmain(the 4-eyes and account-resolution tests) now setofferedInstanceTypes, and the offered-catalog fixtures carry a price, becausemainexcludes unpriced convertible RIs from reshape (#1188/#1191).Verification
All evidence is unit, mock or fixture based. No real AWS calls were made. Tests ran with
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true.New tests:
internal/api/reshape_offered_types_test.go:m5.2xlargeturns 1x m5.4xlarge at 50% into 2x m5.xlarge instead of 1x m5.2xlarge;Reason;OfferedInstanceTypes, including that a partial result returned next to an error is discarded.internal/server/ri_exchange_offered_types_test.go:newRIExchangeClientsroutes every call, includingGetValidResourceTypes;executeRIExchangeReshapeand the ladderrun()(dry run), asserting the offering lookup target, the outcome target and count,Skippedreasons and the warnings.Fail before / pass after (re-checked after the refresh for the ladder path): removing
OfferedInstanceTypes:from the ladder params fails the 4ladder/*subtests; restoring it passes them. The 4-eyes tests from #1108 (TestExchangeRunnerAdapter_FourEyesDowngradesLadderReshape,TestExecuteRIExchangeReshape_FourEyesDowngradesAutoToManualand the 4-eyes-off counterparts) pass on the merged code.Checks after the refresh:
go build ./...,go vet ./...andgo vet -tags integration ./...: clean.go test ./internal/server/ ./internal/api/: pass.go test -tags integration -run 'Exchange|Reshape|OfferedCatalog|FourEyes|RIUtilization' ./internal/server/ ./internal/api/(testcontainers Postgres): pass.golangci-lint run ./internal/server/... ./internal/api/..., with and without--build-tags integration: 0 issues.Combined-tree verification. This branch was tested together with the other queued branches on a local, never-pushed tree:
queue/platform@400d256b= origin/main7b86e386plus these heads, merged in this order:Linux, Go 1.26.9,
AWS_CONFIG_FILE=/dev/null AWS_SHARED_CREDENTIALS_FILE=/dev/null AWS_EC2_METADATA_DISABLED=true; Docker Postgres via testcontainers. The full suite ran onf7c48020, which differs from this tree only by test-only commits on fix/issue-30, fix/issue-713 and fix/issue-263 that replace placeholder account-number literals with named constants:go build ./...,go vet ./...,go vet -tags integration ./...and the fullgo test -race -short -count=1 ./....--build-tags integration.gocyclo -over 10is silent on the changed non-test files.-tags integration -count=1):internal/analytics,internal/api,internal/auth,internal/config,internal/database/postgres/migrations,internal/purchase,internal/schedulerandinternal/serverall pass. Migrations 000149, 000150 and 000151 apply in order on this tree.approval-details,settings-accounts,api) pass (221 tests).accountkey); fixed with named constants. Re-run on this tree: git-secrets, using the repo'sscripts/setup-git-secrets.shpatterns in a fresh repo with an isolated HOME, found nothing in the changed files, and a planted fake key was flagged.scripts/test-git-secrets-allowlist.sh: 183 PASS, 0 FAIL..tftest.hclfiles were not run locally (CI runs them). Nothing was applied to any cloud.Not covered: I didn't run against a real AWS account, so a live
DescribeInstanceTypeOfferingsresponse and the real IAM grant are untested. The production client construction inhandleRIExchangeReshapeandexchangeRunnerAdapter.RunAutoExchangeneeds real AWS config and is not exercised. The compiler checks that*ec2svc.ClientsatisfiesriExchangeEC2, and the tests covernewRIExchangeClientsitself.Out of scope
RIExchangeModevalidation), see above.A maintainer, please mirror the issue's triage labels on this PR:
priority/p2,severity/low,urgency/this-quarter,impact/few,effort/m,type/feat,triaged.🤖 Generated with Claude Code